控制器提供了对应用程序行为(通常是service接口定义)的访问。控制器拦截了用户的输入,将它转成一个传递给用户的试图模型。Spring用非常抽象的方式实现了控制器,使你可以创建各种各样的控制器。
Spring2.5为MVC控制器引入了基于注解的编程模型,比如@RequestMapping
,@RequestParam
和@ModelAttribute
等等。这些注解支持Servlet MVC和Portlet MVC。通过这种方式实现的控制器不需要继承特定的基类或是实现特定的接口。另外,它们通常也不会直接依赖Servlet或是Protlet API,尽管你可以通过简单的配置来访问Servlet或Prolete功能。
在Github上pring-project项目中,许多web应用程序都利用了这节中讨论的注解的支持,它们包括MvcShowcase,MvcAjax,MvcBasic,PetClinic,PetCare等。
@Controller
public class HelloWorldController {
@RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World!");
return "helloWorld";
}
}
正如你所看到的,@Controller
和@RequestMapping
注解允许灵活的方法名称和签名。在这个特殊的例子中,方法接受了Model
并返回了一个String
作为视图的名字,但是本章之后会介绍其他方法参数和返回值也可以被使用。@Controller
和@RequestMapping
和其他一些注解为Spring MVC的实现提供了基础。本节是关于介绍这些注解的文档以及他们通常如何在Servlet环境使用。
@Controller
注解表示这个特殊类的扮演了控制器的角色。Spring并没有要求你的控制器类需要拓展自某个基类或是引用Servlet API。但是,如果你需要的话你仍可以引用Servlet的特性。
@Controller
注解作为注解的类的标记,指明了类的角色。派发器扫描这些被注解的类,检测其方法上的@RequestMapping
注解(见下一节),然后映射这些方法。
你可以在派发器的上下文中通过标准的Spring bean定义来显式的声明一个被注解的控制器bean。但是,@Controller
标记也允许自动检测,这和Spring对类路径下一般的component类进行扫描和注册是一样的。
为了启用对被注解的控制器的自定检测,你需要在你的配置中添加组建扫描。像下面的XML这样使用spring-context schema:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.springframework.samples.petclinic.web"/>
<!-- ... -->
</beans>
你可以使用@ReqeustMapping
注解将诸如/appointment
的URL映射到整个类或是特殊的处理器方法上。通常,类级别的注解将特定的请求路径(或路径样式)映射到控制器上,在通过方法级别的注解将映射缩小到特定的HTTP方法请求上("GET","POST"等)或是HTTP请求参数条件上。
下面的例子来自PetCare,展示了Spring MVC中控制器如何使用注解:
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@RequestMapping(method = RequestMethod.GET)
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
@RequestMapping(path = "/{day}", method = RequestMethod.GET)
public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
@RequestMapping(path = "/new", method = RequestMethod.GET)
public AppointmentForm getNewForm() {
return new AppointmentForm();
}
@RequestMapping(method = RequestMethod.POST)
public String add(@Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
}
这个例子好几处都用到了@RequestMapping
。第一处是在类型(类)级别,表明了这个控制器中的所有处理器方法都和/appointments
路径相关。get()
方法还用@ReqeustMapping
进行了细化:它只接受GET
请求,意味着/appointments
的GET
请求会调用它。add()
方法有个相似的喜欢,getNewForm()
将HTTP方法和路径结合到了一起,因此appointments/new
的GET
请求会被这个方法处理。
getForDay
方法展示了@RequestMapping
的另一个用法:URI模板。(见"URI Tempate Patterns")。
类级别的@RequestMapping
不是必要的。没有它,所有的路径都表示简单的绝对路径,而非相对路径。下面饿例子来自PetClinic:
@Controller
public class ClinicController {
private final Clinic clinic;
@Autowired
public ClinicController(Clinic clinic) {
this.clinic = clinic;
}
@RequestMapping("/")
public void welcomeHandler() {
}
@RequestMapping("/vets")
public ModelMap vetsHandler() {
return new ModelMap(this.clinic.getVets());
}
}
上面的例子没有指定GET
,PUT
,POST
等,因为@RequestMapping
默认映射了所有的HTTP方法。用@RequestMapping(method=GET)
或是@GetMapping
可以细化映射。
Spring 4.3引入了方法级别的@RequestMapping
注解的变体,来帮助简化对通用HTTP方法的映射和更好的表达被注解方法的语义。比如,@GetMapping
可以被理解为一个GET
的@RequestMapping
。
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
* PatchMapping
下面的例子是前一节的AppointmentsController
的修改版本,使用了联合的@RequestMapping
注解。
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@GetMapping
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
@GetMapping("/{day}")
public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
@GetMapping("/new")
public AppointmentForm getNewForm() {
return new AppointmentForm();
}
@PostMapping
public String add(@Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
}
在某些情况,控制器需要在运行时被AOP代理装饰。比如你直接在控制器中使用@Transactional
注解。当遇到这种情况,对于控制器,我们建议使用基于类的代理。对控制器而言,这通常是默认的选择。但是如果一个控制必须要实现一个不是Spring上下文回调的接口(比如,不是InitalizingBean
,*Aware
等),你可能需要显示的配置基于类的代理。比如,对于<tx:annotation-driven/>
而言,要改成<tx:annotation-driven proxy-target-class="true">
。
Spring 3.1为@RequestMapping
方法引入了一组新的支持类,分别是ReqeustMappingHandlerMapping
和RequestMappingHandlerAdapter
。在Spring 3.1之后,它们被推荐甚至被要求使用。新的支持类会被MVN命名空间和MVC Java配置默认启用,但是如果二者都未使用时,就必须显式的配置。本节讨论新老支持类一些重要的不同点。
在Spring 3.1之前,类型(类)和方法级别的请求会在两个部分被检测——首先控制器会被DefaultAnnotationHandlerMapping
选择,之后AnnotataionMethodHandlerAdapter
会选择调用的方法。
Spring 3.1提供了新的支持类,因此调用哪个方法处理请求只在RequestMappingHanderMapping
中决定。将控制器方法看作不同的端点的集合,端点映射到由类和方法级别的@ReqeustMapping
信息派生出的方法。
这也产生了一些新的可能性。HandlerInterceptor
或HandlerExceptionResolver
现在可以预测哪个基于对象的处理作为HandlerMethod
,这允许他们减产方法,包括参数和关联的注解。对URL的处理不再需要分为不同的控制器。
但是它也不再支持一些事:
先通过SimpleUrlHandlerMapping
或是BeanNameUrlHandlerMapping
选择控制器,再由@RequestMapping
注解细化方法。
将方法名称作为回调机制来区分两个没有显式区分映射路径但其他方面(比如HTTP方法)相同的方法。在新的支持类中,@RequestMapping
的方法必须要映射不同的路径。
* 对控制器中没有其他更具体的方法可以匹配,只有一个默认的(没有显式指定映射的)方法,则用该方法处理请求。在新的支持类中,如果没有匹配的方法,则会抛出404错误。
现有的支持类仍然可以支持上述的特性。但是为了使用Spring MVC 3.1的新特性。你必须要使用新的支持类。
URI模板可以方便的访问@RequestMapping
方法中选择的部分。
URI模板是一个URI风格的字符串,包含了一个或多个变量名。当你要替换这些变量时,模板就变成了一个URI。proposed RFC为URI模板定义了URI是如何参数化的。比如,URI模板http://www.example.com/users/{userId}
包含了变量userId。http://www.example.com/user/fred
为变量设置了fred值。
在Spring MVC你可以在方法参数参数上使用@PathVariable
注解来绑定URI模板的变量值:
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
URI模板"/owners/{ownerId}
"指定了变量名为ownerId
。当控制器处理请求时,URI这部分的值会被设置为ownerId
的属性。比如,一个为/owners/fred
的请求进来时,ownerId
会被设置成fred
。
为了处理@PathVariable注解,Spring MVC需要找到符合URI模板的变量名称。你一颗在注解中指定它:
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
// implementation omitted
}
或者如果URI模板的变量名和方法参数的名字匹配,你可以省略指定。只要你的代码在编译时打开了调试信息或是Java 8中的
-parameters
编译标志,Spring MVC会自动匹配方法中的参数名和URI模板的变量名:
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
// implementation omitted
}
一个方法可以有任意多个@PathVariable
注解:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
Pet pet = owner.getPet(petId);
model.addAttribute("pet", pet);
return "displayPet";
}
当@PathVariable
注解对Map<String,String>
参数使用时,所有的URI模板变量会被填充到map。URI模板可以是类型(类)和方法级别的@RequestMapping注解组装成的。findPet()
方法可以被像/owners/42/pets/21
这样的URL请求所调用。
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping("/pets/{petId}")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
被@PathVariable
注解的参数可以是任何简单的类型,比如int
,long
,Date
等等。Spring会为你自动的转换成合适的类型,如果这么失败了,则会抛出TypeMismatchException
。你可以为其他数据类型的解析注册所需的支持。见"Method Parameters And Type Conversion"和"Customizing WebDataBinder initalization"。
有时你需要更细粒度的定义URI模板变量。考虑"/spring-web/spring-web-3.0.5.jar"
这个URL。你会如何将它分隔成多个部分?
@RequestMapping
注解支持用正则表达式表示URI模板变量。以{varName:regex}
的形式,第一部分是变量名,而第二部分是正则表达式。比如:
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
// ...
}
另外,@RequestMapping
注解和所有@RequestMapping
的变体都支持Ant-风格的路径样式(比如,/myPath/*.do
)。同样也支持URI模板变量和Ant-风格样式的组合(比如,/owners/*/pets/{petId}
)。
当一个URL符合多个样式,那么会(对样式)排序以找出最符合的。
更少的URI变量和通配符的样式会被认为更精确。比如,/hotels/{hotel}/*
有一个URI变量和一个通配符,它要比有一个URI变量和两个通配符的/hotels/{hotel}/**
更为精确。
如果两个样式有相同数量(的URI变量和通配符),那么更长的一个,被认为更精确。比如/hotels/{hotel}
比/hotels/*
来的精确。
还有些额外的特殊规则:
默认的映射/**
不如其他样式精确。比如/api/{a}/{b}/{c}
更为精确。
诸如/public/**
这样的前缀样式不如其他不含有双通配符的样式精确。比如/public/path3/{a}/{b}/{c}
更为精确。
详细信息请看AntPathMathcer
中的AntPatternComparator
。注意可以自定义PathMatcher
(见Spring MVC配置那节中的22.16.11,"Path Matching")。
@ReqeustMapping
中的样式支持针对本地属性或是系统属性和环境变量的占位符${...}
。这对于需要根据配置在自定义控制器映射路径的情况是很有用的。更多关于占位符的信息,请查看PropertyPlaceholderConfigurer
类的文档。
Spring默认为样式提供了后缀".*"
,因此/person
同样隐式的映射了/person.*
。这使得通过URL请求不同的资源变得简单。(比如,/persion.pdf
,/person.xml
)。
后缀样式的匹配可以被关闭,也可以被限制成一组明确内容的路径拓展。通常建议将普通请求映射的歧义最小化。比如/person/{id}
,这里的点不一定就表示文件拓展名,像/person/[email protected]
和/persion/[email protected]
。此外,后缀样式匹配和内容商议都可以在某些情况下被用来恶意攻击,因此要限制它们的意义。
见22.16.11节,"Path Matching"了解关于后缀样式匹配的配置和22.16.6节"Content Negotiation",了解关于内容商议的配置。
2014年,反射文件下载(RFD)攻击首次在Trustwave的论文中被描述。这种攻击和XSS很像,都依赖于将输入(比如,查询参数,URI变量)反射到响应中。但和在HTML中插入JavaScript不同,RFD攻击变成了让浏览器下载并基于拓展名(比如.bat,.cmd),将响应当做可以被双击执行的脚本。
在Spring MVC中,@ResponseBody
和ResponseEntity
方法存在风险,因为它们会根据客户端的请求,包括通过URL的路径拓展名,(将响应)渲染成不同的内容类型。但是请注意,无论是禁用后缀样式匹配还是禁用内容协商的路径拓展名的使用都不能有效的防止RFD攻击。
为了全面防止RFD攻击,在响应体被渲染之前,Spring MVC添加了一个头Content-Dispostion:inline;filename=f.txt
来建议一个稳定安全的文件下载名。只有在URL路径包含一个既没有在白名单中也没有显式注册在内容商议中的文件拓展名时,才会这么做。但当浏览器直接输入URL时,这可能有副作用。
许多通用的路径拓展名默认在白名单中,而且REST API通常也不会之家在浏览器中输入URL。尽管如此,使用自定义HttpMessageConverter
实现的应用可以显示的未内容协议注册文件拓展名,也不会为这些拓展名田间Content-Disposition头。见22.16.6节,"Content Negotiation"。
它最初是作为CVE-2015-5211的一部分。以下是报告的其他建议:
对JSON响应进行编码而不是转义。这也是OWASP XSS的建议。有关如何用Spring实现这点的例子请看spring-jackson-owasp。
在配置中关闭后缀样式匹配或是限制为只显示注册的后缀。
将内容协议的"useJaf"和"ignoreUnkownPathExtensions"设置为false,这会在对于未知拓展名的URL请求产生406的响应。但是请注意,对于本身以点结尾的URL来说这不是个好主意。
为响应添加X-Content-Type-Options:nosniff
头。Spring Security 4中默认这么做了。
URI规范RFC3986定义了路径片段中包含姓名属性值的键值对的可能性。规范中没有规定特定的术语(称呼它)。通常可以叫做"URI路径参数",在Tim Berners-Lee的一份旧的帖子中提出的"Matrix URIs",也经常被使用和广泛的了解。在Spring MVC中,我们用matrix variables的概念。
Matrix variables可以出现在任何路径片段中,每个matrix variable用";"(分号)分隔。比如:"/cars;color=red;year=2012"
。多个值可以通过","(逗号)分隔"color=red,green,blue"
或者重复使用变量名表示"color=red;color=green;color=blue"
。
如果URL中会包含matrix variables,那么请求映射样式必须要用URI模板中表示。它确保了请求可以被正确的匹配而不管matrix variables是否存在,或是以何种顺序存在。
下面是提取matrix variable "q"的例子:
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
由于所有的路径片段都可能包含matrix variables,在一些情况下你需要更确切的定义martrix variables出现的位置:
// GET /owners/42;q=11/pets/21;q=22
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable(name="q", pathVar="ownerId") int q1,
@MatrixVariable(name="q", pathVar="petId") int q2) {
// q1 == 11
// q2 == 22
}
matrix variable可以被定义为可选的并指定一个默认值:
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
可以填充所有的matrix variables到Map中:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable MultiValueMap<String, String> matrixVars,
@MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) {
// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 11, "s" : 23]
}
注意为了使用matrix variables,你必须将ReqeustMappingHandlerMapping
的removeSemicolonContent
的属性设为false
。它默认被设为true
。
MVC Java配置和MVC命名空间都提供了启用matrix variables的选项。
如果你在使用Java config,Advanced Customization with MVC Java Config这节描述了如何自定义RequestMappingHandlerMapping
。
对于MVC命名空间,<mvc:annotation-driven>
元素有一个enable-matrix-variables
的属性应该要被设置true
。它默认被设为false
。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven enable-matrix-variables="true"/>
</beans>
你可以通过制定可消化的媒体类型列表来缩小主映射的范围。只有请求头的Content-Type
符合制定的媒体类型,请求才算符合。比如:
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet, Model model) {
// implementation omitted
}
可消化的媒体类型的表达式也可以用!text/plain
的形式确定,它匹配所有除了Content-Type
为text/plain
之外的请求。也可以考虑使用MediaType
提供的常量,比如APPLICATION_JSON_VALUE
和APPLICATION_JSON_UTF8_VALUE
。
可消费类型的条件对类和方法级别(的注释)都支持。其他大部分的条件不同,当使用在类级别时,方法级别的可消费类型会覆盖类级别的定义,而不是拓展它。
你可以通过指定生成的媒体类型列表来缩小主映射的范围。只有在Accept
请求头符合其中一个值时请求才会被匹配。另外,使用生成条件确保响应的实际的内容类型需要遵循生成条件。比如:
@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
// implementation omitted
}
注意,生成条件中的媒体类型中也可以选择指定字符集。比如,上面的代码中我们指定了和
MappingJackson2HttpMessageConverter
默认配置相同的媒体类型,同时包括了UTF-8
字符集。
和consumes一样,生成的媒体类型的表达式也可以用!text/plain
这样的形式确定,他匹配了所有所有Accept
头不为text/plain
的请求。也可以考虑使用MediaType
中提供的常量,比如APPLICATION_JSON_VALUE
和APPLICATION_JSON_UTF8_VALUE
。
produces条件也同时支持类和方法界别。不像其他大部分的条件,当使用在类级别,方法级别的生成类型会覆盖它而不是拓展它。
你可以通过像"myParam"
,"!myParam"
,或是myParam=myValue
这样的参数条件还缩小请求匹配的范围。上面前两个表示存在/不存在这样的参数,第三个指定了参数的值。下面是一个关于参数值条件的例子:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
它同样可以用来测试请求头是否存在/不存在或是基于特定的请求头的值:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
尽管你可以在媒体类型中使用通配符来匹配Content-Type或是Accept头(比如,"content-type=text/"会匹配"text/plain"和"text/html"),但是建议使用consumes和produces*时直接指定。它们就是出于这个目的被设计的。
映射到"GET"的@RequestMapping
方法同样隐式的映射到了"HEAD",因此不需要显式的声明"HEAD"。HTTP HEAD请求会像HTTP GET请求一样被处理,但只有字节数被写入响应体并设置"Content-Length"头。
@RequestMapping
方法同样内置了对HTTP OPTIONS的支持。默认的情况下,HTTP OPTIONS请求通过显式在@RequestMapping
方法上声明HTTP方法来匹配URL样式,并让请求得以被处理。如果没有显式的设置HTTP方法,那么"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"都是允许的。最好总是直接在@ReqeustMapping
中声明打算处理的HTTP方法,会是选择使用专用的@RequestMapping
变体(见"Composed @RequestMapping Variants"这节)。
尽管没有必要,但可以将@RequestMapping
方法映射到并处理HTTP HEAD或HTTP OPTIONS,或两者同时。
@RequestMapping
处理器方法可以有很领过的签名。支持的方法参数和返回值将在接下来讨论。大部分参数都可以以任意的顺序使用,除了BindingResult
参数。这会在霞姐讨论。
Spring 3.1引入了
@RequestMapping
新的支持类,分别是RequestMappingHandlerMapping
和RequestMappingHandlerAdapter
。它们被建议使用甚至要求使用它们从而使用Spring MVC 3.1之后的新特性。新的支持类会被MVC命名空间和MVC Java配置默认使用,如果没有使用这两者的话需要显式配置。
下面是支持的方法参数:
(Servlet API)Request或Response对象.可以选择任何具体的request活response类型,比如ServletRequest
或是HttpServletRequest
。
(Servlet API)session对象:HttpSession
的类型。这种类型的参数表示对应的Session。因此,这种参数永远不会为null
。
在特殊的Servlet环境中,对Session的访问可能不是线程安全的。如果多个请求可能同时访问session,那么可以考虑将
RequestMappingHandlerAdapter
的"synchronizeOnSession"标签设为"true"。
org.springframework.web.context.request.WebRequest
或org.springframework.web.context.request.NativeWebRequest
。它允许通用的请求参数的访问以及request/session属性的访问,而不需要和Servlet/Portlet API耦合。 java.util.Locale
,表示当前请求的语言环境,由最具体的语言环境解析器(在MVC环境中,被配置成LocaleResolver/LocaleContextResolver
)决定。 java.util.TimeZone
(Java 6+)/java.time.ZoneId
(Java 8),表示了当前请求关联的时区,由LocaleContextResolver
决定 java.io.InputStream
/java.io.Reader
,用来访问请求的内容,参数值是由Servlet API暴露出的原生InputStream/Reader。 java.io.OutputStream
/java.io.Reader
用来生成响应的内容。参数值是有Servelt API暴露出的原生的OutputStream/Writer。 org.springframework.http.HttoMethod
用来表示HTTP请求方法。 java.security.Principal
包含当前授权的用户。 @PathVariable
注解的参数,表示URI模板变量,见"URI Template Patterns"。 @MatrixVariable
注解的参数,表示URI路径片段中的键值对。见"Matrix Variables"。 @RequestParam
注解的参数表示特定的Servlet请求参数。参数值会被转换成被声明的方法参数的类型。见"Binding reqeust parameters to method parameters with @ReqeustParam" @RequestHeader
注解的参数表示特定的Servlet请求的HTTP头。参数值被转成声明的方法参数类型。见"Mapping request header attributes with the @RequestHeader annotation"。 @RequestBody
注解的参数表示HTTP请求体。参数值会被HttpMessageConverter
转成被声明的方法参数类型。 @ReqeustPart
注解的参数用来访问"multipart/form-data"请求部分的内容。见22.10.5,"Handling a file upload request from programmatic clients"和22.10,"Spring`s multipart(file upload) support"。 SessionAttribute
注解访问长久存在的session属性(比如用户授权对象),而不是通过@SessionAttributes
将模型的属性暂时存在session,作为控制器流的一部分。 ReqeustAttribute
注解的参数表示请求的属性。 HttpEntity<?>
参数可以访问Servlet请求的HTTP头和内容。请求的流会被HttpMessageConverter
转到entity body中。见"Using HttpEntity"。 java.util.Map
/org.springframework.ui.Model
/org.springframework.ui.ModelMap
表示暴露给视图的模型。 org.springframework.web.servlet.mvc.support.RedirectAttributes
用来指定在重定向时使用的属性集合,并且可以添加flash属性(临时存储在服务器端并让他们在重定向之后仍可用)。见"Passing Data To the Redirect Targt"和22.6节,"Using flash attributes"。 @InitBinder
方法和HandlerAdapter配置,将请求参数绑定bean属性上(通过setter方法)或直接绑定到字段。见RequestMappingHandlerAdapter
的webBindingInitializer
属性,这些命令对象和他的校验结果默认会被作为模型的属性,通过使用命令对象的类名称——比如,模型属性"orderAddress"表示了"some.package.OrderAddress"的命令对象。ModelAttribute
注解可以在方法参数中使用来自定义所使用的模型属性名称。 org.springframework.validation.Errors
/org.springframework.validation.BindingResult
,之前的命令或表单对象的校验结果(紧邻的前面一个方法参数)。 org.springframework.web.bind.support.SessionStatus
,表示表单处理的状态句柄,如果状态已完成,会触发对处理器类级别的由@SessionAttribute
注解指定的Session属性的清理。 org.springframework.web.util.UriComponentsBuidler
,相对于当前请求的主机,端口,scheme,上下文路径和servlet映射的URL builder。 Errors
或BindingResult
参数在必须紧跟着被绑定的模型对象,如果方法签名中有多个模型对象,Spring会为每个都创建独立的BindingResult
,因此下面的代码是无效的:
@PostMapping
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }
注意,在Pet
和BindingResult
之间有个Model
参数。为了让代码有用,你需要重新排列一下参数:
@PostMapping
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }
JDK 1.8的
java.util.Optional
支持作为有reqequired
属性注解(比如,@RequestParam
,@ReqeustHeader
等)标注的方法属性。在这种情况下使用java.util.Optional
和required=false
意思是一致的。
下面是支持的返回类型:
ModelAndView
对象,该模型隐式地增加了命令对象以及@ModelAttribute注释的引用结果的数据访问的方法。
Model
对象,RequestToViewNameTranslator
会隐式的决定视图的名字,并且隐式的增加了命令对象以及@ModelAttribute注释的引用结果的数据访问的方法。
Map
对象,表示暴露出的模型,ReqeustToViewTranslator
会隐式的决定使用的名字,并且增加了命令对象以及@ModelAttribute注释的引用结果的数据访问的方法。
View
对象,通过命令对象和@ModelAttribute
注解隐式确定模型。处理器方法也可以通过声明模型参数以编程的方式来丰富模型。
String
,表示一个逻辑视图名,伴随着一个由命令对象和@ModelAttribute
注解确定的模型来访问数据。处理器方法也可以通过声明模型参数以编程的方式来丰富模型。
void
,如果一个方法自己处理响应(直接写响应的内容,为此声明了类型为ServletResponse
/HttpServletResponse
的参数),或是视图名可以隐式的被ReqeustToViewNameTranslator
决定(没有在处理器的方法签名中声明response参数)。
如果方法被@ResponseBody
注解,返回值会被写入response HTTP体。返回值会被HttpMessageConverter
转换成声明方法的参数类型。见"Mapping the response body with the @ResponseBody annotation"。
HttpEntity<?>
或ResponseEntity<?>
对象,来访问Servlet response HTTP头和内容。entity body会被HttpMessageConverter
转成response的流。见"Using HttpEntity"。
HttpHeaders
对象,返回不含有响应体的响应。
Callable<?>
,当应用程序希望处理结果有Spring MVC管理的线程异步的产生时使用。
DeferredResult<?>
,在应用程序希望以自己选择的线程来产生返回值时使用。
ListenableFuture<?>
或CompletableFuture<?>
/CompletionStage<?>
,在应用程序希望返回值由线程池中的一个任务产生时使用。
ResponseBodtEmitter
可以异步的返回多个对象到响应中;支持作为ResponseEntity
的主体。
SseEmitter
可以异步的将Server-Sent事件写入响应;支持作为ResponseEntity
的主体。
StreamingResponseBody
可以异步的将输出流写入响应中;支持作为ResponseEntity
的主体。
任何其他返回类型被视为暴露给视图的单个模型属性,使用在方法级别通过@ModelAttribute指定的属性名称(或基于返回类型类名称的默认属性名称)。模型隐式的增加了命令对象以及@ModelAttribute注释的引用结果的数据访问的方法。
使用@ReqeustParam
注解乐意在你的控制器中将请求参数绑定到方法的参数中。
下面展示了用法:
@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {
// ...
@GetMapping
public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
默认使用这个注解的参数都是必须的,但是你可以通过设置@RequestParam
的required
属性为false
将这个参数设为可选。(比如,@ReqeustParam(name="id", required=false)
)。
如果目标方法的参数如果不是String
,那么类型转换会被自动应用。见"Method Parameters And Type Conversion"。
当Map<String, String>
或MultiValueMap<String, String>
参数使用了@ReqeustParam
注解时,map会被请求参数所填充。
@ReqeustBody
表示方法参数和HTTP方法参数请求体绑定。比如:
@PutMapping("/something")
public void handle(@RequestBody String body, Writer writer) throws IOException {
writer.write(body);
}
请求体被HttpMessageConverter
转成方法参数。HttpMessageConverter
负责将Http请求转成对象,以及将对象转成Http响应体。ReqeustMappingHandlerAdapter
通过下列默认的HttpMessageConverters
支持@RequestBody
注解:
ByteArrayHttpMessageConverter
转换字节数组。
StringHttpMessageConverter
转换字符串。
FormHttpMessageConverter
转成MultiMap
SourceHttpMessageConverter
,和javax.xml.transform.Source相互转换。
关于这些转换器的更多信息,见Message Converters。注意,如果使用MVC命名空间或是MVC Java配置,默认会注册更多的message converters。见22.16.1节,"Enabling the MVC Config or the MVC Namespace"获取更多的信息。
如果你想要读或写XML,你需要配置MarshallingHttpMessageConverter
,并指定来自org.springframework.oxm
包中的Marshaller
和Unmarshaller
实现。下面的列子展示了如何配置,但是如果你的应用使用了MVC命名空间或是MVC Java配置,那么见22.16.1,"Enabling the MVC Java Config or the MVC XML NameSpace")。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<util:list id="beanList">
<ref bean="stringHttpMessageConverter"/>
<ref bean="marshallingHttpMessageConverter"/>
</util:list>
</property
</bean>
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean id="marshallingHttpMessageConverter"
class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<property name="marshaller" ref="castorMarshaller"/>
<property name="unmarshaller" ref="castorMarshaller"/>
</bean>
<bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>
@RequestBody
方法参数可以结合@Valid
参数使用,这种情况下,配置的Validator
实例会检验它。当使用MVC命名空间或是MVC Java配置时,假设类路径上有JSR-303可用的实现,那么JSR-303校验器会被自动的配置。
和@ModelAttribute
参数一样,Errors
ca念书可以用来检查错误。如果没有声明这个参数,那么会抛MethodArgumentNotValidException
异常。异常会被DefaultHandlerExceptionResolver
,它会抛出400异常到客户端。
同样的,关于在MVC命名空间或是MVC Java配置中配置message converters和validator见22.16.1节,"Enabling the MVC Java Config or the MVC XML Namespace"。
@ResponseBody
注解和@RequestBody
很相似。这个注解用在方法上,表明返回值直接写进HTTP response(不是到Model中,或是作为视图名)。比如:
@GetMapping("/something")
@ResponseBody
public String helloWorld() {
return "Hello World";
}
上面的例子直接将Hello World
文本写进了Http响应流。
对于@RequestBody
,Spring通过HttpMessageConverter
将返回的对象转换至响应体。更多转换器的信息,见前一节和Message Converters。
用控制器来实现一个只提供JSON,XML或自定义的媒体内容的REST API很常见。为了方便起见,你可以将你的控制器类加上@RestController
注释,而不用在你所有的@RequestMapping
方法中添加@ResponseBody
注释。
@RestController
结合了@ResponseBody
和@Controller
。另外,在框架未来的版本中,它可能为你的控制器添加更多的含义和语义。
和其他常规的@Controller
一样,@RestController
可以由@ControllerAdvice
或@RestControllerAdvice
协助。见"Advising controllers with @ControllerAdvice and @RestControllerAdvice"这节获得更多信息。
HttpEntity
和@RequestBody
和@ResponseBody
类似。除了可以访问请求体和响应体之外,HttpEntity
(和特定于响应的子类ResponseEntity
)也允许访问请求头和响应头,像这样:
@RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader"));
byte[] requestBody = requestEntity.getBody();
// do something with request header and body
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}
上面的例子中,MyReqeustHeader
的请求头中取值,并从请求体重读取字节数组。它将MyResponseHeader
添加到响应,将Hello World
写入响应流,并将响应状态设置为201(已创建)。
同@ReqeustBody
和@ResponseBody
一样,Spring使用HttpMessageConverter
来转换请求和响应的流。更多关于转换器的信息,见前一节和Message Converters。
@ModelAttribute
注释可以用在方法或是方法参数上。本节解释它在方法上的用法,下一节解释他在方法参数上的用法。
方法上的@ModelAttribute
注解表名这个方法为添加一个或多个model属性。这些方法支持和@RequestMapping
方法一样的参数类型,但不能直接映射请求。实现上,一个控制器中的@ModelAttribute
方法会在同个控制器的@ReqeustMapping
方法调用前调用。一些列子:
// Add one attribute
// The return value of the method is added to the model under the name "account"
// You can customize the name via @ModelAttribute("myAccount")
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountManager.findAccount(number);
}
// Add multiple attributes
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountManager.findAccount(number));
// add more ...
}
@ModelAttribute
方法通常用来为model添加常用的属性,比如,根据状态或是宠物类型来填充一个下拉菜单,或是检索一个命令对象,比如Account,来表示HTML表单里的数据。后面这种情况会在下一节中讨论。
注意,以上是@ModelAttribute
方法的两种方式。第一种,方法将返回值隐式的添加到了属性中。第二种,方法接受了Model
参数,通过它可以添加任意数量的模型属性。你可以根据你的西药选择哪种方式。
一个控制器可以有任意数量的@ModelAttribute
的方法。它们都在同一个控制器的@ReqeuestMapping
方法调用前被调用。
@ModelAttribute
方法可以定义在@ControllerAdvice
注解的类中,并且方法将被应用到多个控制器。见"Advising controllers with @ControllerAdvice and @RestControllerAdivce"了解更多信息。
如果没有显示指定model的属性名称会怎么样呢?这种情况下,会要根据其类型分配一个默认的属性名称。比如,如果方法返回了
Account
类型的对象,默认的名字是"account"。你可以通过@ModelAttribute
注解的值来指定属性名称。如果是直接添加属性到Model
,可以使用addAttribute(..)
方法合适的重载形式-比如,带有或不带有属性名的形式。
@ModelAttribute
注解也可以用在@RequestMapping
方法上。这种情况下,@ReqeustMapping
方法的返回值会作为模型的属性而不再是视图的名称。然后,会根据视图名称规定决定视图的名称,这可方法返回了void
很像——见22.13.3节,"Default view name"。
正像前一节所说的,@ModelAttribute
可以用在方法和方法参数上。这节将介绍它在方法参数上的用法。
用在方法参数上的@ModelAttribute
表示这个参数应该从模型中检索。如果在模型中不存在这个参数,那会初始化这个参数,并添加到模型中。一旦模型中存在,那么这个参数的字段会匹配请求中的同名参数,并被填充。这就是Spring MVC中的数据绑定,这个机制让你不必再解析表单各个字段,从而省去很多工作。
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }
上面给出的例子中,Pet实例是从哪来的?下面是一些选项:
由于使用了@SessionAttributes
,可能已经存在在模型中——见"Using @SessionAttributes to store model attributes in the HTTP session between reqeust一节"。
由于相同控制器中使用了@ModelAttribute
方法,它可能已经存在于模型中——见前一节。
由URL模板变量和类型转换检索(之后将会解释)。
会根据它默认的构造器初始化
@ModelAttribute
的方法是从数据库检索属性的常用方法,还可以通过@SessionAttributes
对其进行存储然后在请求间传递。一些情况下,通过URI模板变量和类型转换器检索属性十分方便:
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
// ...
}
在这个例子中,模型的属性名(即"account")匹配URI模板变量的名称。如果你注册了Converter<String, Account>
,能够将String
类型的account值转成Account
实例,那么上面的例子不需要@ModelAttribute
。
下一步是数据绑定。WebDataBinder
类会匹配请求参数名称——包括查询字符创参数和表达的域——根据名称转成模型的属性字段。在需要的时候,类型转换器会填充匹配的字段。设计数据绑定和校验的内容在第九章,Validation, Data Binding, and Type Conversion。控制器层面的自定义数据绑定程序在"Customizing WebDataBinder initialization"中介绍。
数据绑定可能会引起注入必须的字段缺失或是类型转换失败的错误。为了监察这些错误,可以在@ModelAttribute
参数之后立即跟上BindingResult
:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
通过BindingResult
,你可以检查是否有错误,(如果有错误),通常的方式是在相同的表单用Spring的<error>
标签渲染。
注意,在某些情况下可能会需要不进行数据绑定。对于这种情况,你可以将Model
注入到控制器或是选择使用注解的binding
标志:
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
@ModelAttribute(binding=false) Account account) {
// ...
}
除了数据绑定之外,你还可以调用自定义的校验器,同样传入用来记录数据绑定错误的BindingResult
。这允许数据绑定和校验的错误保存在同一个地方,之后向用户反馈:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "petForm";
}
// ...
}
或者你可以通过添加JSR-303的@Valid
注解自动校验:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
见9.8节,"Spring Validation"和第九章,Validation, Data Binding, and Type Conversion了解详情及如何配置和使用校验。
类级别的@SessionAttributes
注解声明了特定处理器使用的session属性。它通常会列举出模型属性的名称或是类型,并透明的存储在session或对话存储中,在后续的请求中传递。
下面的例子演示了这个注解的用法,指定了模型属性的名称:
@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
如果你需要访问全局管理的(即在控制器之外)预存在的session属性,它有可能存在也可能不存在,请在方法参数上使用@SessionAttribute
属性:
@RequestMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
对于需要添加或是删除session属性的情况,请考虑将org.springframework.web.context.request.WebRequest
或javax.servlet.http.HttpSession
注入到控制器方法中。
为了将模型属性作为控制器工作流的一部分短暂存储在session,请考虑使用SessionAttributes
,见"Using @SessionAttributes to store model attributes in the HTTP session between reqeusts"。
和@SessionAttribute
类似,@RequestAttribute
注解可以用来访问由过滤器或拦截器创建的预存在的请求属性:
@RequestMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
前面的小节介绍了使用@ModelAttribute
来支持浏览器对表单的提交。对于非浏览器的客户端也建议使用这个注解。然而它们在处理HTTP PUT请求时有一个显著的区别。浏览器可以通过HTTP GET或HTTP PUT请求提交表单数据。非浏览器客户端只能通过HTTP PUT请求提交表单数据。这是一个挑战,因为Servlet规范要求ServletRequest.getParameter*()
系列方法只支持HTTP POST,而不支持HTTP PUT。
为了支持HTTP PUT和PATCH请求,spring-web
模块提供了HttpPutFormContentFilter
过滤器,可以在web.xml
配置:
<filter>
<filter-name>httpPutFormFilter</filter-name>
<filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpPutFormFilter</filter-name>
<servlet-name>dispatcherServlet</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
上述过滤器拦截了内容类型为application/x-www-form-urlencoded
的HTTP PUT和PATCH请求,从请求体重读取表单数据,并封装ServletRequest
,使得ServletRequest.getParameter*()
系列方法可用。
由于
HttpPutFormContentFilter
消耗了请求体,因此不应该为类型为application/x-www-form-urlencoded
的PUT或PATCH请求配置其他转换器。这包括@RequestBody MultiValueMap<String, String>
和HttpEntity<MultiValueMap<String, String>>
。
@CookieValue
注解允许绑定HTTP cookie的值到方法参数中。
考虑下列http请求收到的cookie:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
下面的列子展示了如果获取JSESSIONID
cookie的值:
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
//...
}
如果参数的类型不是String
,类型转换器会自动应用。见"Method Parameters And Type Conversion"。
这个注解对Servlet和Protlet环境下的处理器方法都支持。
@ReqeustHeader
注解支持将请求头绑定到方法参数。
一个请求头的样本:
Host localhost:8080
Accept text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
下面的代码演示了如从请求头中获取Accept-Encoding
和Keep-Alive
的值:
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
如果方法参数不是String
类型,那么会自动类型转换。见"Method Parameters And Type Conversion"。
当@ReqeustHeader
注解应用在Map<String, String>
,MultiMap<String, String>
或HttpHeaders
参数上时,请求头中所有的值都会被填充到map中。
内置了将逗号分隔的字符串转成数组或集合或其他系统已知的转换类型的支持。比如,
@ReqeustHeader("Accept")
的参数了能是String
,也可能是String[]
或List<String>
。
这个注解同时支持Servlet和Portlet环境下的处理器方法。
从请求中提取出来的字符串类型的值(包括请求参数,路径变量,请求头和cookie值)会被转换成它们所绑定的方法参数或字段(@ModelAttribute
参数中的字段)的类型。如果目标类型不是String
,Spring会自动转换成合适的类型。支持所有的简单类型,像int,long,Date等。你也可以通过WebDataBinder
自定义转换过程。(见"Customizing WebDataBinder initialization")或是通过FormattingConversionService
注册Formatters
(见9.6节,"Spring Field Formatting")。
为了通过Spring的WebDataBinder
使用PropertyEditors自定义请求参数的绑定,可以在你的控制器内或是在@ControllerAdvice
类中增加一个@InitBinder
注解的方法,或是提供一个自定义的WebBindingInitializer
。见"Advising controllers with @ControllerAdvice and @RestControllerAdvice"了解更多信息。
在控制器方法中添加@InitBinder
是你可以在你的控制器类中直接配置数据绑定。@InitBinder
标注的方法初始化了WebDataBinder
,用来为处理器方法填充命令或是表单对象。
除了command/form对象和对应的校验结果兑现之外,init-binder的方法支持所有@ReqesutMapping
方法支持的参数。Init-binder方法不能够有返回值。因此,它们总是被声明为void
。通常参数会包含WebDataBinder
和WebReqeust
或java.util.Local
的组合,使代码可以注册特定于上下文的编辑器。
下面的例子演示了使用@InitBinder
为表单中所有java.util.Date
属性配置了CustomDateEditor
。
@Controller
public class MyFormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
在Spring4.2中,你可以考虑选择addCustomFormatter
指定Formatter
的实现代理PropertyEditor
实例。如果你恰好在共享的FormattingConversionService
中使用基于Formatter
的设置,那么这种方法特别有用,同样的方法可以重复用于控制器特定的绑定规则调整。
@Controller
public class MyFormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
为了拓展属性绑定初始化过程,你可以提供WebBindInitializer
接口的自定义实现,然后为AnnotationMethodHandlerAdapter
提供自定义Bean配置来启用它,由此覆盖默认的配置。
下面的例子来自PetClinic应用,展示了WebBindingInitializer
接口的自定义实现的配置,org.springframework.samples.petclinic.web.ClinicBindingInitializer
,它为一系列的PetClinic控制器配置了所需的PropertyEditors。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="cacheSeconds" value="0"/>
<property name="webBindingInitializer">
<bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer"/>
</property>
</bean>
@InitBinder
方法可以定义在@ControllerAdvice
注解的方法中,以便应用到多个匹配的控制器中。这为使用WebBindingInitializer
提供了其他选择。见"Advising controllers with @ControllerAdvice and @RestControllerAdvice"了解更多。
@ControllerAdvice
注解是一个组建注解,它允许在类路径下自动检测它的实现类。当使用MVC命名空间或MVC Java配置时,它被自动启用。
@ControllerAdvice
注解的类可以包含@ExceptionHandler
,@InitBinder
,和@ModelAttribute
注解的方法,这些方法会应用于所有控制器层次结构中的@ReqeustMapping
方法中,而非它们声明的控制器层次结构中。
也可以选择@RestControllerAdvice
,它会将@ExceptionHandler
方法默认为@ResponseBody
。
@ControllerAdvice
和@RestControllerAdvice
都可以指定一部分控制器:
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}
查看@ControllerAdvice
文档获取更多信息。
有时候将上下文对象过滤并序列化到HTTP响应体中会很有用。为了提供这个功能,Spring MVC内置对[Jacksons Serialization Views](http://wiki.fasterxml.com/JacksonJsonViews)渲染的支持。
为了在
@ResponseBody控制器方法或返回
ResponseEntity的控制器方法中使用它,只需要添加
@JsonView`注解在相应的类上,并指出视图的类或接口:
@RestController
public class UserController {
@GetMapping("/user")
@JsonView(User.WithoutPasswordView.class)
public User getUser() {
return new User("eric", "7!jd#h23");
}
}
public class User {
public interface WithoutPasswordView {};
public interface WithPasswordView extends WithoutPasswordView {};
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
@JsonView(WithoutPasswordView.class)
public String getUsername() {
return this.username;
}
@JsonView(WithPasswordView.class)
public String getPassword() {
return this.password;
}
}
注意,尽管
@JsonView
允许指定多个类,但控制器方法上只允许指定一个类参数。如果你需要使用多个视图,请考虑使用符合接口。
对于依赖视图解析(返回视图名)的控制器方法而言,只需要添加序列化视图类到model中:
@Controller
public class UserController extends AbstractController {
@GetMapping("/user")
public String getUser(Model model) {
model.addAttribute("user", new User("eric", "7!jd#h23"));
model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
return "userView";
}
}
为了@ResponseBody
和ResponseEntity
方法启用对JSONP的支持,声明一个@ControllerAdvice
的bean,继承自AbstractJsonpResponseBodyAdvice
,像下面这样,控制器方法参数指明了JSONP的查询参数名:
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super("callback");
}
}
对于返回视图名的控制器,当请求中含有jsonp
或callback
的查询参数名称是,JSONP会自动启用。这些名称可以通过jsonpParameterNames
属性定义。
Spring MVC 3.2引入了基于Servlet 3的异步请求处理。控制器方法通常不再返回一个值,而是可以返回一个java .util.concurrent.Callable
,并通过Spring MVC管理的线程产生返回值。同时,Servlet容器的线程会退出并释放,来允许其他请求。Spring MVC在TaskExecutor
的帮助下通过独立的线程调用Callable
,当Callable
返回,请求会被重新分发回Servlet容器,并继续处理Callable
返回的值。下面是一个例子:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
控制器方法也可以返回一个DeferredResult
的实例。这种情况下,返回值可以有任意线程产生,即,不被Spring MVC管理的线程。比如,结果可能由外部一些事件产生,比如JMS message,调度任务等等。下面是这种控制器方法的另一个例子:
@RequestMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
// Save the deferredResult somewhere..
return deferredResult;
}
// In some other thread...
deferredResult.setResult(data);
理解Servlet 3.0匿名请求处理特性可能有些困难。以下是关于潜在机制的一些基本原理:
ServletReqeust
可以通过调用request.startAsync()
而进入异步模式。这么做最主要的影响是Servlet,和任何Filter会退出,但是响应仍旧打开等待之后请求处理完整。
调用request.startAsync()
会返回AsyncContext
用来进一步控制异步处理。比如它提供了dispatch
方法,它和Servlet API的转发类似,只是它允许应用程序通过Servlet容器的线程继续处理请求。
* ServletRequest
可以访问当前DispatcherType
,可以区分初始请求,异步调度,转发和其他调度类型。
了解了上面的内容,下面介绍使用Callable
异步处理请求的处理流程:
控制器返回一个Callable
。
Spring MVC开始异步处理,并将Callable
提交到TaskExecutor
,让独立的线程处理。
DispatcherServlet
和所有的过滤器退出Servlet容器线程,但是响应仍旧打开。
Callable
产生结果,Spring MVC将请求分发回Servlet容器继续处理。
* DispatcherServlet
再次被调用,继续处理由Callable
异步产生的结果。
DefferedResult
的处理过程也类似,除了用任意线程来处理异步结果:
控制器返回一个DeferredResult
并存储在内存的队列或列表中,以备之后的访问。
Spring MVC开始异步的处理。
DispatcherServlet
和所有配置的过滤器退出请求处理的线程,但是响应任然保持打开。
应用程序利用其它线程调度DeferredResult
,并且Spring MVC将请求派发回Servlet容器。
* DispatcherServlet
再次被调用,继续处理异步产生的结果。
有关异步处理请求的动机或是何时以及为什么的信息,请参考这里的系列博文。
如果控制器返回的一个Callable
在执行时引发了异常会怎么办?简单来说它和一个控制器方法中引发异常是同样的。它经历了常规的异常处理机制。更复杂的解释是当一个Callable
引发了异常,Spring MVC会将Exception
作为结果派发回Servlet容器,然后处理异常的流程不是返回值。当使用DeferredResult
时,你可以选择是调用setResult
还是setErrortResult
并传递Exception
实例。
HandlerInterceptor
也可以实现AsyncHandlerInterceptor
来实现afterConcurrentHandlingStarted
回调,这个方法会在异步处理开始时调用,而不是在postHandle
或是afterCompletion
时。
HandlerInterceptor
也可以注册CallableProcessingInterceptor
或是DeferredResultProcessingInterceptor
来集成更深层的异步请求的生命周期,比如处理一个超时事件。见AsyncHandlerInterceptor
的文档获取更多信息。
DeferredResult
类型也提供了像onTimeout(Runnable)
和onCompletion(Runnable)
之类的方法。见DeferredResult
的文档获取更多信息。
当使用Callable
时,你可以通过WebAsyncTask
的实例包装它,它提供了超时和完成的注册方法。
控制器方法可以使用DeferredResult
和Callable
来异步的产生返回值,可以利用它实现长轮询的技术,使得服务器可以尽快推送数据到客户端。
如果你想将多个事件推动到同一个HTTP响应中会怎么样?这被称为"Long Polling",也叫作"HTTP Streaming"。Spring MVC通过返回类型为ResponseBodyEmitter
的值实现了这点,这种类型可以被用来发送多个对象,而不是像普通的@ResponseBody
那样只能有一个对象,每个对象通过HttpMessageConverter
被写入到响应。
下面是一个例子:
@RequestMapping("/events")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Save the emitter somewhere..
return emitter;
}
// In some other thread
emitter.send("Hello once");
// and again later on
emitter.send("Hello again");
// and done at some point
emitter.complete();
注意,ResponseBodyEmitter
也可以作为ResponseEntity
的主体来自定义响应的状态和头。
SseEmitter
是ResponseBodyEmitter
的子类,提供了对Server-Sent Events的支持。Servet-sent事件是"HTTP Streaming"技术的一种变体,它从服务器推送的事件要符合W3C Server-Sent Event规范。
Server-Sent事件可以在其他用途下使用,比如从服务器推送时间到客户端。在Spring MVC中做到这点很简单,只需要将返回值改成SseEmitter
类型。
注意,IE不支持Server-Sent时间,对于更高级的web应用,比如在线游戏,协作,财务应用等,最好考虑Spring的WebSocket,包括SockJS-风格的WebSocket模拟消息推送至支持大部分的浏览器(包括IE),以及更高级的通过发布订阅模型实现的与客户端的交流。更多消息见博文。
ResponseBodyEmitter
允许通过HttpMessageConverter
将对象写入响应。通常的情况下都是这样,比如写入JSON数据。但是有时候绕过消息转换,直接写入到响应的OutputStream
会很有用,比如文件下载。这个通过返回类型为StreamingResponseBody
的对象实现。
下面是一个例子:
@RequestMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
注意StreamingResponseBody
可以作为ResponseEntity
的主体来自定义响应的状态和头。
通过web.xml
配置的项目要确认更新的3.0的版本:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
...
</web-app>
通过开启web.xml
中DispatcherServlet
的子元素<async-supported>true</async-supported>
实现对异步的处理。另外参与异步处理的过滤器必须要被配置成指出ASYNC dispatcher的类型。为Spring Framework提供的所有过滤器启用ASYNC dispatcher类型应该是安全的,因为它们通常会扩展OncePerRequestFilter,并且运行时检查过滤器是否需要参与异步分派。
下面是web.xml配置的实例:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<filter>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.~.OpenEntityManagerInViewFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>
</filter-mapping>
</web-app>
如果使用Servlet 3和基于Java的配置比如通过WebApplicationInitializer
,你也需要像web。xml
这样设置"asyncSupported"标识和ASYNC dispatcher类型。为了简化这些配置,你可以继承AbstractDispatcherServletInitializer
,或者更好的是继承AbstractAnnotationConfigDispatcherServletInitializer
,它会自动设置这些选项,并让注册Filter
实例变得简单。
MVC Java配置和MVC命名空间提供了异步请求处理的选项。WebMvcConfigurer
有一个configureAsyncSupport
的方法,<mvc:annotation-driven>
也有<async-support>
的子元素。
这些都允许你配置默认异步请求的超时时间,如果没有设置的话就取决于Servlet容器(比如,Tomcat是10秒)。你可以配置一个AsyncTaskExecutor
用来执行从控制器方法返回的Callable
实例。强烈建议配置这个属性,因为Spring MVC默认使用了SimpleAsyncTaskExecutor
。MVC Java配置和MVC命名空间同样允许你注册CallableProcessingInterceptor
和DefereedResultProcessingInterceptor
实例。
如果你想为某个特定的DefferedResult
覆盖默认的超时时间,你可以选择合适的构造器来实现。类似的,对于Callable
,你可以使用合适改造函数将它包装成WebAsyncTask
来指定超时时间。WebAsyncTask
的累构造器也允许提供AsyncTaskExecutor
。
spring-test
模块提供了测试被注解的控制器的支持。见15.6节,"Spring MVC Test Framework"。